Amazon LexにCloudFormationでLambda関数を関連付ける場合、リソースベースポリシーも記載が必要です
はじめに
以前、Amazon Connectに、Amazon LexボットとAWS Lambda関数をAWS CloudFormationや AWS CLIで関連付け方法をブログで紹介しました。
この記事を参考に、CloudFormationでLexボットとLexから呼び出すLambdaを作成しようとした際、LexボットがLambdaを呼び出すためには、Lambdaのリソースベースポリシーを明示的にCloudFormationテンプレートに記載する必要がわかりました。
この追加設定が必要な理由と具体的な設定内容を紹介します。
CloudFormationで作成してみる
以下にCloudFormationテンプレートを紹介します。
なお、Connectインスタンスは事前に作成済みであることを前提としています。
テンプレートの主な内容は次の通りです。
- 以下を構築
- Lexボット
- ボット、バージョン、エイリアス
- Lexボットが使用するロググループとS3バケット
- Lambda
- Lexボットから呼び出されるLambda
- IAMロール
- Lexボット用IAMロール
- Lambda用IAMロール
- Lexボット
- ConnectからLexボットを呼ぶため、LexをConnectインスタンスに関連づける
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Create Lambda, Lex bot, and associate them with Connect instance'
Parameters:
ConnectInstanceARN:
Type: String
Description: The Amazon Resource Name (ARN) of the instance.
ProjectName:
Type: String
Resources:
TestLambdaFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Sub ${ProjectName}-test-lambda
Handler: index.lambda_handler
Role: !GetAtt TestLambdaExecutionRole.Arn
Code:
ZipFile: |
~省略~
Runtime: python3.12
MemorySize: 512
Timeout: 10
TestLambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${ProjectName}-test-lambda-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
TestLex:
Type: AWS::Lex::Bot
Properties:
Name: !Sub ${ProjectName}-test-lex
DataPrivacy:
ChildDirected: false
IdleSessionTTLInSeconds: 300
RoleArn: !GetAtt TestLexRole.Arn
BotLocales:
- LocaleId: ja_JP
NluConfidenceThreshold: 0.40
VoiceSettings:
VoiceId: Kazuha
Intents:
- Name: FallbackIntent
ParentIntentSignature: AMAZON.FallbackIntent
DialogCodeHook:
Enabled: true
- Name: dummy
SampleUtterances:
- Utterance: dummy
DialogCodeHook:
Enabled: true
TestLexVersion1:
Type: AWS::Lex::BotVersion
Properties:
BotId: !Ref TestLex
BotVersionLocaleSpecification:
- LocaleId: ja_JP
BotVersionLocaleDetails:
SourceBotVersion: DRAFT
TestLexAlias:
Type: AWS::Lex::BotAlias
Properties:
BotAliasLocaleSettings:
- LocaleId: ja_JP
BotAliasLocaleSetting:
Enabled: true
CodeHookSpecification:
LambdaCodeHook:
LambdaArn: !GetAtt TestLambdaFunction.Arn
CodeHookInterfaceVersion: '1.0'
BotAliasName: dev
BotId: !Ref TestLex
BotVersion: !GetAtt TestLexVersion1.BotVersion
ConversationLogSettings:
TextLogSettings:
- Enabled: true
Destination:
CloudWatch:
CloudWatchLogGroupArn: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${TestLexCloudWatchLogGroup}'
LogPrefix: dev/
AudioLogSettings:
- Enabled: true
Destination:
S3Bucket:
S3BucketArn: !GetAtt TestLexS3Bucket.Arn
LogPrefix: dev/
TestLexCloudWatchLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/lex/${ProjectName}-test-lex
RetentionInDays: 365
TestLexS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${ProjectName}-test-lex-${AWS::AccountId}
TestLexRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${ProjectName}-test-lex-role
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: lexv2.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: !Sub ${ProjectName}-test-lex-policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- polly:SynthesizeSpeech
Resource: '*'
- Effect: Allow
Action:
- s3:PutObject
Resource:
- !Sub 'arn:aws:s3:::${TestLexS3Bucket}/*'
- Effect: Allow
Action:
- logs:CreateLogStream
- logs:PutLogEvents
Resource:
- !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${TestLexCloudWatchLogGroup}:*'
TestLexAssociation:
Type: AWS::Connect::IntegrationAssociation
Properties:
InstanceId: !Ref ConnectInstanceARN
IntegrationType: LEX_BOT
IntegrationArn: !GetAtt TestLexAlias.Arn
# Lambdaのリソースベースポリシー
# TestLambdaPermission:
# Type: AWS::Lambda::Permission
# Properties:
# Action: lambda:InvokeFunction
# FunctionName: !GetAtt TestLambdaFunction.Arn
# Principal: lexv2.amazonaws.com
# SourceArn: !GetAtt TestLexAlias.Arn
# SourceAccount: !Ref AWS::AccountId
このテンプレートを使用してCloudFormationスタックを作成し、ConnectフローからLexを呼び出しても、フローはエラー状態に遷移してしまいます。
エラーの原因を特定するため、以下のログを確認しましたが、明確な理由は判明しませんでした。
- Connectフローログ:具体的な理由は記載されず、"Error"しか出力されません。
- Lexのロググループ:ログの出力がありません。
- Lexが呼び出すLambdaのロググループ:ログの出力がありません。
さらに調査を進め、CloudTrailのLambdaデータイベントを確認したところ、LexからLambdaを呼び出す際(Invoke API使用時)に"AccessDenied"エラーが発生していることが判明しました。
Lambdaのリソースベースポリシー
原因を調査した結果、LexがLambdaを呼び出す際に必要なリソースベースポリシーが、Lambdaに設定されていなかったことが判明しました。
エラーを解決するために、先ほど紹介したCloudFormationテンプレートに以下を追加します。
この追加により、LexからLambdaを呼び出す権限が付与され、ConnectフローからLexボットを適切に起動できるようになります。
TestLambdaPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !GetAtt TestLambdaFunction.Arn
Principal: lexv2.amazonaws.com
SourceArn: !GetAtt TestLexAlias.Arn
SourceAccount: !Ref AWS::AccountId
Lambdaのリソースベースのポリシーを追加後、コンソールからは、以下の通り、設定内容がjson形式で確認できます。
{
"StringEquals": {
"AWS:SourceAccount": "123456789012"
},
"ArnLike": {
"AWS:SourceArn": "arn:aws:lex:ap-northeast-1:123456789012:bot-alias/<ボットID>/<エイリアスID>"
}
}
Connectから関連付ける場合
Connectインスタンスに直接関連付けるLexやLambdaの場合も紹介します。
Connectインスタンスに直接関連付けるLambdaの場合、CloudFormationでは、今回のようにLambdaのリソースベースポリシーを追加する必要はありません。自動でリソースベースが追加されました。
{
"StringEquals": {
"AWS:SourceAccount": "123456789012"
},
"ArnLike": {
"AWS:SourceArn": "arn:aws:connect:ap-northeast-1:123456789012:instance/xxxxxxxxxx"
}
}
Connectインスタンスに直接関連付けるLexに関しても、CloudFormationテンプレートで関連付けを行うと、Lexのリソースベースポリシーが自動で追加されていました。
作成したLexのエイリアスからリソースベースポリシー内容が確認できます。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "connect-ap-northeast-1-xxxxxxxx",
"Effect": "Allow",
"Principal": {
"Service": "connect.amazonaws.com"
},
"Action": [
"lex:RecognizeText",
"lex:StartConversation"
],
"Resource": "arn:aws:lex:ap-northeast-1:123456789012:bot-alias/<ボットID>/<エイリアスID>",
"Condition": {
"StringEquals": {
"AWS:SourceAccount": "123456789012"
},
"ArnEquals": {
"AWS:SourceArn": "arn:aws:connect:ap-northeast-1:123456789012:instance/xxxxxxxx"
}
}
}
]
}
Amazon Lex ボットを Amazon Connect インスタンスに関連付けると、ボットのリソースベースのポリシーが更新され、Amazon Connect にボットを呼び出すアクセス許可が付与されます。
https://docs.aws.amazon.com/ja_jp/connect/latest/adminguide/amazon-lex.html
Lexボットのサービスロール
LexボットがLambdaを呼び出すためには、Lambdaのリソースベースポリシーを追加する必要があると説明しました。
では、LexボットのIAMロールにLambdaを呼び出す権限を追加する方法は適切ではないのでしょうか?
Amazon Lexのドキュメントによると、ユーザーがサービスロールを編集・変更することは推奨されていません。
サービスロールのアクセス許可を変更すると、Amazon Lex V2 の機能が破損する可能性があります。Amazon Lex V2 が指示する場合以外は、サービスロールを編集しないでください。
サービスロールを変更した場合、Lexの機能が正常に動作しない可能性があります。
したがって、Lexのサービスロールを変更するのではなく、Lambdaのリソースベースポリシーを適切に設定することが推奨される方法です。
まとめ
本記事のまとめです。
- CloudFormationでLexボットがLambda関数を呼び出すには、リソースベースポリシーをテンプレートで明示的に設定する必要があります。
- 一方、CloudFormationでConnectインスタンスに関連付けるLexボットやLambdaの場合、リソースベースポリシーは自動的に作成されます。
これらの点に注意することで、確実にAWSリソース間の連携を構築できます。
参考